package coap.client;

import coap.Configuration;
import coap.messages.Message;
import coap.messages.MessageType;
import coap.messages.MethodCodes;
import coap.network.MessageHandler;
import coap.network.TimeoutChecker;
import coap.network.TimeoutHandler;
import coap.network.UDPNetwork;

import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.HashMap;

/**
 * Created by IntelliJ IDEA.
 * User: iodine
 * Date: Jan 21, 2012
 * Time: 7:16:45 PM
 * To change this template use File | Settings | File Templates.
 */
public class ClientSession implements MessageHandler, TimeoutHandler {
    UDPNetwork network = null;
    InetAddress address = null;
    HashMap<Integer, Message> outstandingQueue = new HashMap<Integer, Message>();
    TimeoutChecker checker = null;
    CoAPClient client;


    public ClientSession(String server, CoAPClient client) {
        this.client = client;
        try {
            address = InetAddress.getByName(server);
            checker = new TimeoutChecker(this);
            checker.start();
            network = new UDPNetwork(-1, this);
            network.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public synchronized void sendMessage(Message msg, boolean resend) {
        System.out.println("send message "+msg);
        if (msg.getType() == MessageType.Confirmable)
            outstandingQueue.put(msg.getMessageID(), msg);
        byte[] b = new byte[Configuration.MAX_PACKET_SIZE];
        ByteBuffer buf = ByteBuffer.wrap(b);
        msg.serialize(buf);

        network.send(b, 0, buf.position(), address, Configuration.PORT);
        if (msg.getType() == MessageType.Confirmable && !resend)
            checker.add(msg);
    }


    public synchronized void handleMessage(byte[] b, int len, InetAddress sender, int senderPort) {
        ByteBuffer buf = ByteBuffer.wrap(b, 0, len);
        Message msg = new Message(buf);
        System.out.println("handle "+msg);
        switch (msg.getType()) {
            case MessageType.Confirmable:
                if (!msg.isResponse())
                    throw new RuntimeException("weird "+msg);
                Message messageRespond = outstandingQueue.remove(msg.getMessageID());
                if (messageRespond == null) {
                    System.out.println("receive an unknown response " + msg);
                    Message reset = new Message(MessageType.Reset, 0, msg.getMessageID(), new byte[0]);
                    this.sendMessage(reset, false);
                } else {
                    Message reply = new Message(MessageType.Acknowledgement, 0, msg.getMessageID(), new byte[0]);
                    sendMessage(reply, false);
                    client.handleResponse(msg);
                }
                break;
            case MessageType.NonConfirmable:
                if (!msg.isResponse())
                    throw new RuntimeException("weird");
                client.handleResponse(msg);
                break;
            case MessageType.Acknowledgement:
                if (msg.getCode() != 0) { //piggybacked
                    Message messageAcked = outstandingQueue.remove(msg.getMessageID());
                    if (messageAcked == null) {
                        System.out.println("receive an unknown piggyback ack " + msg);
                        Message reset = new Message(MessageType.Reset, 0, msg.getMessageID(), new byte[0]);
                        this.sendMessage(reset, false);
                    } else {
                        checker.remove(messageAcked);
                        client.handleResponse(msg);
                    }
                } else {
                    Message messageAcked = outstandingQueue.get(msg.getMessageID());
                    if (messageAcked == null) {
                        System.out.println("receive an unknown ack " + msg);
                        System.out.println("silently ignored");
                    } else {
                        checker.remove(messageAcked);
                        client.handleAck(msg);
                    }
                }
                break;
            case MessageType.Reset:
                System.out.println("Unexpected reset message");
                break;
        }
    }

    public synchronized void timeout(Message msg, boolean overLimit) {
        if(msg.getType()!=MessageType.Confirmable)
            throw new RuntimeException("weird");
        System.out.println("timeout "+msg.getMessageID());
        if(!overLimit)
            sendMessage(msg, true);
        else{
            this.outstandingQueue.remove(msg);
            client.failed(msg);
        }
    }
}
